﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection.Emit;

namespace Aeon.Emulator.Decoding.Recompiler
{
    internal sealed class LoopCompiler
    {
        private readonly RegisterExpressions registers;

        public LoopCompiler(RegisterExpressions registers)
        {
            this.registers = registers;
        }

        public void GenerateLoop(string name, CompilerContext context, Label? target, uint targetAddress, ILGenerator il)
        {
            Label doneLabel = il.DefineLabel();
            DecrementCX(context.AddressSize, il);
            registers.GenerateValue(context.AddressSize == 16 ? "CX" : "ECX", il);

            il.Emit(OpCodes.Brfalse_S, doneLabel);

            switch(name)
            {
            case "loop":
                break;

            case "loope":
                Conditions.AndFlags(EFlags.Zero, il);
                il.Emit(OpCodes.Brfalse_S, doneLabel);
                break;

            case "loopne":
                Conditions.AndFlags(EFlags.Zero, il);
                il.Emit(OpCodes.Brtrue_S, doneLabel);
                break;

            default:
                throw new InvalidOperationException();
            }

            IncrementCounter(context, target, targetAddress, il);

            il.MarkLabel(doneLabel);
        }

        private void IncrementCounter(CompilerContext context, Label? target, uint targetAddress, ILGenerator il)
        {
            if(target != null)
            {
                il.LoadLocal(context.CounterInt);
                il.LoadConstant(1);
                il.Emit(OpCodes.Add);
                il.StoreLocal(context.CounterInt);

                il.LoadLocal(context.CounterInt);
                il.LoadConstant(context.MaxCount);
                il.Emit(OpCodes.Clt);
                il.Emit(OpCodes.Brtrue, (Label)target);

                this.registers.GenerateReference("EIP", il);
                il.LoadConstant((int)targetAddress);
                il.Emit(OpCodes.Stind_I4);

                il.LoadLocal(context.CounterInt);
                il.Emit(OpCodes.Ret);
            }
            else
            {
                this.registers.GenerateReference("EIP", il);
                il.LoadConstant((int)targetAddress);
                il.Emit(OpCodes.Stind_I4);

                il.LoadLocal(context.CounterInt);
                il.Emit(OpCodes.Ret);
            }
        }

        private void DecrementCX(int addressSize, ILGenerator il)
        {
            if(addressSize == 16)
            {
                registers.GenerateReference("CX", il);
                registers.GenerateValue("CX", il);
                il.Emit(OpCodes.Conv_I2);
                il.LoadConstant(1);
                il.Emit(OpCodes.Sub);
                il.Emit(OpCodes.Conv_I2);
                il.Emit(OpCodes.Stind_I2);
            }
            else if(addressSize == 32)
            {
                registers.GenerateReference("ECX", il);
                registers.GenerateValue("ECX", il);
                il.LoadConstant(1);
                il.Emit(OpCodes.Sub);
                il.Emit(OpCodes.Conv_U4);
                il.Emit(OpCodes.Stind_I4);
            }
            else
                throw new InvalidOperationException();
        }
    }
}
